/*******************************************************************************
 * Copyright (c) 2014 Steffen Mikkelsen
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     Steffen Mikkelsen - initial API and implementation
 ******************************************************************************/
package com.subsecret.secureconnection.streamtools;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.DecimalFormat;
import java.util.List;

import com.subsecret.secureconnection.client.ClientConnInfo;
import com.subsecret.secureconnection.securestream.SecureStream;
import com.subsecret.secureconnection.securestream.SecureStream.SecureStreamLayerMonitor;

public class StreamTools {
    
    public static void setupSocketClientSide(Socket socket, int serverrequestport, String username, String password)
            throws IOException {
        sendString(socket.getOutputStream(), username);
        sendString(socket.getOutputStream(), password);
        sendInteger(socket.getOutputStream(), serverrequestport);
    }
    
    public static class LoginInformation {
        public String username = "";
        public String password = "";
        public int port;
        
        public LoginInformation(String user, String pass, int port) {
            this.username = user;
            this.password = pass;
            this.port = port;
        }
    }
    
    public static LoginInformation setupSocketServerSide(Socket socket) throws IOException {
        String user = receiveString(socket.getInputStream());
        String pass = receiveString(socket.getInputStream());
        int port = receiveInteger(socket.getInputStream());
        return new LoginInformation(user, pass, port);
    }
    
    public static void sendString(OutputStream out, String str) throws IOException {
        sendByteArray(out, str.getBytes());
    }
    
    public static void sendInteger(OutputStream out, int num) throws IOException {
        sendInteger(out, num, true);
    }
    
    public static void sendInteger(OutputStream out, int num, boolean flush) throws IOException {
        out.write(intToByteArray(num));
        out.flush();
    }
    
    public static void sendShort(OutputStream out, short num) throws IOException {
        sendShort(out, num, true);
    }
    
    public static void sendShort(OutputStream out, short num, boolean flush) throws IOException {
        out.write(shortToByteArray(num));
        out.flush();
    }
    
    public static void sendByte(OutputStream out, byte b, boolean flush) throws IOException {
        out.write(b);
        if (flush) {
            out.flush();
        }
    }
    
    public static void sendByte(OutputStream out, byte b) throws IOException {
        sendByte(out, b, true);
    }
    
    public static byte receiveByte(InputStream in) throws IOException {
        return (byte) in.read();
    }
    
    public static String receiveString(InputStream in) throws IOException {
        return new String(receiveByteArray(in));
    }
    
    public static byte[] receiveByteArray(InputStream in) throws IOException {
        int size = receiveShort(in);
        if (size == StreamConstants.SIZE_LARGER_THAN_SHORT) {
            size = receiveInteger(in);
        }
        if (size > 1000000) {
            throw new IOException("Too large array specified");
        }
        byte[] res = new byte[size];
        for (int i = 0; i < res.length; i++) {
            res[i] = (byte) in.read();
        }
        return res;
    }
    
    public static void sendMultipleByteArrays(OutputStream out, byte[]... arrays) throws IOException {
        int totLen = 0;
        for (int i = 0; i < arrays.length; i++) {
            totLen += arrays[i].length;
        }
        // send size of array
        if (totLen > Short.MAX_VALUE) {
            sendShort(out, StreamConstants.SIZE_LARGER_THAN_SHORT, false);
            sendInteger(out, totLen, false);
        }
        else {
            sendShort(out, (short) totLen, false);
        }
        
        for (byte arr[] : arrays) {
            out.write(arr);
        }
        out.flush();
    }
    
    public static void sendByteArray(OutputStream out, byte[] array) throws IOException {
        // send size of array
        if (array.length > Short.MAX_VALUE) {
            sendShort(out, StreamConstants.SIZE_LARGER_THAN_SHORT, false);
            sendInteger(out, array.length, false);
        }
        else {
            sendShort(out, (short) array.length, false);
        }
        
        out.write(array);
        
        out.flush();
    }
    
    public static int receiveInteger(InputStream in) throws IOException {
        byte[] intArray = new byte[4];
        intArray[0] = (byte) in.read();
        intArray[1] = (byte) in.read();
        intArray[2] = (byte) in.read();
        intArray[3] = (byte) in.read();
        return byteArrayToInt(intArray);
    }
    
    public static short receiveShort(InputStream in) throws IOException {
        byte[] intArray = new byte[2];
        intArray[0] = (byte) in.read();
        intArray[1] = (byte) in.read();
        return byteArrayToShort(intArray);
    }
    
    public static int byteArrayToInt(byte[] b) {
        return b[3] & 0xFF | (b[2] & 0xFF) << 8 | (b[1] & 0xFF) << 16 | (b[0] & 0xFF) << 24;
    }
    
    public static byte[] intToByteArray(int a) {
        return new byte[] { (byte) ((a >> 24) & 0xFF), (byte) ((a >> 16) & 0xFF), (byte) ((a >> 8) & 0xFF), (byte) (a & 0xFF) };
    }
    
    public static short byteArrayToShort(byte[] b) {
        return (short) (b[1] & 0xFF | (b[0] & 0xFF) << 8);
    }
    
    public static byte[] shortToByteArray(short a) {
        return new byte[] { (byte) ((a >> 8) & 0xFF), (byte) (a & 0xFF) };
    }
    
    private static DecimalFormat df = new DecimalFormat("#.##");
    
    public static String formatDouble(double val) {
        return df.format(val);
    }
    
    public static int BUFFER_SIZE = 81920;
    
    public static void closeWithoutException(InputStream in) {
        try {
            in.close();
        }
        catch (Exception e) {
        }
        
    }
    
    public static void closeWithoutException(OutputStream out) {
        try {
            out.close();
        }
        catch (Exception e) {
        }
    }
    
    public static void closeWithoutException(Socket appSock) {
        try {
            appSock.close();
        }
        catch (Exception e) {
        }
        
    }
    
    /*
     * public static void getConnectionStatus(String title, int sleepTime, MonitorableOutputStream monOut, ForwardDataThread toOther,
     * MonitorableInputStream monIn, ForwardDataThread fromOther) throws InterruptedException { monOut.resetBytesTransferred();
     * monIn.resetBytesTransferred(); toOther.resetBytesTransferred(); fromOther.resetBytesTransferred(); Thread.sleep(sleepTime);
     * double compSent = 0; if (toOther.getBytesTransferred() > 0) { compSent = 100.0 * ((double) monOut.getBytesTransferred()) /
     * ((double) toOther.getBytesTransferred()); } double compRecv = 0; if (fromOther.getBytesTransferred() > 0) { compRecv = 100.0 *
     * ((double) monIn.getBytesTransferred()) / ((double) fromOther.getBytesTransferred()); }
     * 
     * System.out.println(title + ": Sent " + toOther.getBytesTransferred() + " (CompSize:" + StreamTools.formatDouble(compSent) +
     * "%)  Received " + fromOther.getBytesTransferred() + " (CompSize:" + StreamTools.formatDouble(compRecv) + "%)");
     * 
     * }
     */
    
    public static String padNumber(int num, int i) {
        String number = "" + num;
        while (number.length() < i) {
            number = "0" + number;
        }
        return number;
    }
    
    public static void startMonitoringStream(final String type, final int connNumber, final int localSocketPort,
            final ClientConnInfo ci, final SecureStream createSecureStreamClient) {
        Thread monT = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(5000);
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    List<SecureStreamLayerMonitor> monitors = createSecureStreamClient.getMonitors();
                    StringBuilder builder = new StringBuilder("\n");
                    builder.append("Connection " + connNumber + " | " + type + ": -L " + "localhost:" + localSocketPort + ":"
                            + ci.remoteHost + ":" + ci.remotePort + " @ " + ci.serverHost + ":" + ci.serverPort + "\n");
                    
                    for (SecureStreamLayerMonitor mon : monitors) {
                        builder.append((mon.getBytesSent() / 1024) + " / " + (mon.getBytesReceived() / 1024) + " - "
                                + mon.getDescription() + "\n");
                    }
                    
                    System.out.println(builder.toString());
                }
            }
        };
        monT.start();
        
    }
    
}
